#include "log.hpp"
#include "socket.hpp"
#include <iostream>
#include <algorithm>
#include <cstring>
#include <poll.h>
extern Log lg;

class pollServer
{
public:
    pollServer(uint16_t port)
        :_port(port) 
    {
        // 初始化pollFd
        for(int i = 0; i < fdNumMax; ++i)
        {
            _pollFds[i].fd = -1;
            _pollFds[i].events = nonEvent;
        }
    }
    ~pollServer() = default;

    bool init()
    {
        _listenSock.bind(8080);
        if(_listenSock.listen() == -1)
            return false;
        // 初始化listen 套接字
        _pollFds[0].fd = _listenSock.fd();
        _pollFds[0].events = POLLIN;
        _listenSock.set_no_time_wait();
        return true;
    }
public:
    bool start()
    {
        // 这里不能直接accept，因为这样还是阻塞等待
        int listenfd = _listenSock.fd();

        while(true)
        {
            // 我关心的是读事件是否就绪，所以要解决读事件就绪问题
            // 设置-1表示阻塞等待
            // 如果数组中文件描述符是-1, 那么poll不会进行处理
            int n = poll(_pollFds, fdNumMax, -1);
            switch(n)
            {
                case 0:   // 超时，没有事件
                    printf("time out\n");
                    break;
                case -1:   // 出现异常
                    std::cerr << "poll fail" << std::endl;
                    break;
                default:
                    // 如果不处理，select将会一直提醒用户
                    eventDispatcher();
                    break;
                    
            }
        }
    }
private:
    void linkAccepter()
    {
        // 说明有新的链接到来，接收新连接，并标记比特位
        std::string srcIp; uint16_t srcPort;
        int sockFd = _listenSock.accept(&srcIp, &srcPort);
        if(sockFd == -1)
        {
            lg(Warning, "listenSock accept fail, %d, str: %s", errno, strerror(errno));
            return;
        }
        lg(Info, "get a new link, fd: %d", sockFd);
        // printFd();
        // 找到辅助数组中为空的位置，标记上去
        int i = 0;
        for(; i < fdNumMax; ++i)
            if(_pollFds[i].fd == defaultFd)
            {
                _pollFds[i].fd = sockFd;
                _pollFds[i].events = POLLIN;
                break;
            }
        if(i == fdNumMax)
            lg(Info, "server fd is full!");
    }
    // 处理读事件
    void readHandler(int pos)
    {
        int fd = _pollFds[pos].fd;
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof buffer - 1);  // 这里存在问题，不一定一次都完整
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a message:" << buffer << std::endl;
        }
        else if(!n)
        {
            lg(Info, "client close the fd %d", fd);
            _pollFds[pos].fd = -1;
            _pollFds[pos].events = nonEvent;
        }
        else
        {
            lg(Warning, "read fd %d fail!", fd);
            _pollFds[pos].fd = -1;
            _pollFds[pos].events = nonEvent;
        }
    }
private:
    void eventDispatcher()
    {
        for(int pos = 0; pos < fdNumMax; ++pos)
        {
            struct pollfd fdNature = _pollFds[pos];
            int fd = fdNature.fd;
            if(fd == -1) continue;
            bool flag = fdNature.revents;
            if(flag & POLLIN)
            {
                if(fd == _listenSock.fd())
                    linkAccepter();
                else
                    readHandler(pos);
            }
        }
    }
private:
    static const int defaultFd = -1;
    static const int fdNumMax = 64;
    static const int nonEvent = 0;
private:
    tcp_socket _listenSock;
    uint16_t _port;
    // 由于要标记文件描述符，所以需要用一个辅助数组来存储各个位置的文件描述符
    // poll解决了等待的文件描述符因为接口的原因有上限的问题
    struct pollfd _pollFds[fdNumMax];

};